package net.fkrahe.graph;

import br.ufrgs.inf.inf05005.automatos.AutomNode;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * Class WLinked Graph (Weighted Linked Graph)
 * 
 * @author Yedidyah Langsan | Moshe Augenstein | Aaron M. Tenenbaum
 */
public class WLinkedGraph extends Graph {

    public WLinkedGraph() {
        super();
    }

    /**
     * Creates an arc between node1 and node2 with the given weight
     * 
     * @param infoObj1
     * @param infoObj2
     * @param weight 
     */
    public void join(Object infoObj1, Object infoObj2, String weight) {
        if (weight == null) {
            weight = "";
        }
        GraphNode gn1 = findNode(infoObj1);
        GraphNode gn2 = findNode(infoObj2);
        LinkedList<ArcNode> arcList = gn1.getArcptr();
        WArcNode arcNode = new WArcNode(gn2, weight);

        // If the graph node is not connected to anything, create the arc
        if (arcList == null) {
            LinkedList<ArcNode> list = new LinkedList<ArcNode>();
            list.addFirst(arcNode);
            gn1.setArcptr(list);
        } else if (arcList.isEmpty()) {
            arcList.addFirst(arcNode);
        } else {

            Iterator<ArcNode> itrWAn = arcList.iterator();
            while (itrWAn.hasNext()) {
                WArcNode tmp = (WArcNode) itrWAn.next();
                if (tmp.equals(arcNode)) {
                    arcNode.setWeight(weight);
                    return;
                }
            }
            // connect node1 and node2
            arcList.addLast(arcNode);
        }
    }

    /**
     * Returns the weight on the arc between node1 and node2.
     * If node1 is not connected to node2 throws a WLinkedGraphException
     * 
     * @param node1
     * @param node2
     * @return
     * @throws WLinkedGraphException 
     */
    public String remove(Object node1, Object node2)
            throws WLinkedGraphException {
        // if node1 is not adjacent to node2, throw an exception
        if (!adjacent(node1, node2)) {
            throw new WLinkedGraphException(node1.toString() + " is not connected to " + node2.toString());
        }

        String w = getWeight(node1, node2);
        GraphNode gn1 = findNode(node1);
        GraphNode gn2 = findNode(node2);
        LinkedList<ArcNode> arcList = gn1.getArcptr();
        // node1 is not connected to anything, so there is
        // nothing to remove
        if (arcList == null || arcList.isEmpty()) {
            throw new WLinkedGraphException(node1.toString() + " is not connected to any other node.");
        }
        arcList.remove(new WArcNode(gn2, null));
        return w;
    }

    /**
     * Return the weight on the arc between node1 and node2.
     * If node1 is not connected to node2 throws a
     * WLinkedGraphException
     * 
     * @param infoObj1
     * @param infoObj2
     * @return
     * @throws WLinkedGraphException 
     */
    public String getWeight(Object infoObj1, Object infoObj2) throws WLinkedGraphException {
        GraphNode gn1 = findNode(infoObj1);
        GraphNode gn2 = findNode(infoObj2);
        LinkedList<ArcNode> arcList = gn1.getArcptr();
        Iterator<ArcNode> itr = arcList.iterator();

        while (itr.hasNext()) {
            WArcNode arcNode = (WArcNode) itr.next();
            if (arcNode.getPtr() == gn2) {
                return arcNode.getWeight();
            }
        }
        // node 1 and node 2 are not connected
        throw new WLinkedGraphException(infoObj1.toString() + " is not connected to " + infoObj2.toString());
    }

    // automato
    public GraphNode getRoot() {
        return graphNodes.element();
    }

    public String getErFromGraph() {
        WLinkedGraph graphBk = new WLinkedGraph();
        LinkedList<GraphNode> finalNodesList = new LinkedList<GraphNode>();
        // copy nodes
        Iterator<AutomNode> itrNodes = this.nodes();
        while (itrNodes.hasNext()) {
            AutomNode tmp = itrNodes.next();
            AutomNode newNode = new AutomNode(tmp.getName(), tmp.isIsFinal());
            graphBk.add(newNode);
            // quick test: is final?
            if (newNode.isIsFinal()) {
                finalNodesList.add(new GraphNode(newNode));
            }
        }
        // copy transitions
        Iterator<GraphNode> itrGraph = graphNodes.iterator();
        while (itrGraph.hasNext()) {
            GraphNode graphNode = itrGraph.next();
            AutomNode node11nfo = (AutomNode) graphNode.getInfo();
            String node1Name = node11nfo.getName();// nome nodo1

            LinkedList<ArcNode> arcList = graphNode.getArcptr();
            if (arcList != null) {
                Iterator<ArcNode> itrArc = arcList.iterator();
                while (itrArc.hasNext()) {
                    WArcNode arc = (WArcNode) itrArc.next();
                    String weight = arc.getWeight();// peso arco

                    GraphNode node2 = arc.getPtr();
                    AutomNode node2Info = (AutomNode) node2.getInfo();
                    String node2Name = node2Info.getName();// nome próximo nodo

                    graphBk.join(new AutomNode(node1Name), new AutomNode(node2Name), weight);
                }
            }
        }

        System.out.println("Cópia:");
        System.out.println(graphBk.toString());
        // System.out.println("Root: " + graphBk.getRoot().toString());

        // ensure a unique name for final state, and add it
        String finalNodeName = "qf";
        while (graphBk.add(new AutomNode(finalNodeName, true)) == false) {
            finalNodeName += "f";
        }
        // insert new final state
        GraphNode finalNode = graphBk.findNode(new AutomNode(finalNodeName));
        AutomNode newFinalObj = (AutomNode) finalNode.getInfo();
        // mark all finals as non-final
        Iterator<GraphNode> itrFinalNodes = finalNodesList.iterator();
        while (itrFinalNodes.hasNext()) {
            GraphNode node = itrFinalNodes.next();
            AutomNode obj = (AutomNode) node.getInfo();
            obj.setIsFinal(false);// set not final
            graphBk.join(obj, newFinalObj, null);
        }

        System.out.println("Com novo estado final");
        System.out.println(graphBk.toString());

        // Fucking do it

        // copy transitions
        Iterator<GraphNode> itrGraph2 = graphNodes.iterator();
        while (itrGraph2.hasNext()) {
            GraphNode graphNode = itrGraph2.next();
            AutomNode node11nfo = (AutomNode) graphNode.getInfo();
            String node1Name = node11nfo.getName();// nome nodo1

            // get graphNode connections
            LinkedList<ArcNode> arcList = graphNode.getArcptr();
            if (arcList != null) {
                Iterator<ArcNode> itrArc = arcList.listIterator();
                while (itrArc.hasNext()) {
                    WArcNode arc = (WArcNode) itrArc.next();
                    String weight = arc.getWeight();// peso arco

                    GraphNode node2 = arc.getPtr();
                    AutomNode node2Info = (AutomNode) node2.getInfo();
                    String node2Name = node2Info.getName();// nome próximo nodo

                    graphBk.join(new AutomNode(node1Name), new AutomNode(node2Name), weight);
                }
            }
        }

        GraphNode tst = graphBk.getRoot();
        tst = simplifyOr(graphBk.getRoot());

        System.out.println("simplifyOr aplicado à raiz");
        System.out.println(graphBk.toString());

        return "ER";
    }

    private GraphNode simplifyOr(GraphNode theNode) {
//    public void simplifyOr(GraphNode theNode) {

        LinkedList<ArcNode> oldArcList = theNode.getArcptr();
        Iterator<ArcNode> itrOldArcList = oldArcList.iterator();
//        LinkedList<GraphNode> grafNodeList = new LinkedList<GraphNode>();

        LinkedList<ArcNode> newArcList = new LinkedList<ArcNode>();
        // TODO if !hasNext(); return the node
        while (itrOldArcList.hasNext()) {
            WArcNode oldArc = (WArcNode) itrOldArcList.next();
            GraphNode oldPointedNode = oldArc.getPtr();
            Iterator<ArcNode> newItrArc = newArcList.iterator();
            if (!newItrArc.hasNext()) {
                newArcList.add(oldArc);
            } else {
                boolean join = false;
                GraphNode newPointedNode = null;
                WArcNode newArc = null;
                while (newItrArc.hasNext()) {
                    join = false;
                    newArc = (WArcNode) newItrArc.next();
                    newPointedNode = newArc.getPtr();
                    if (newPointedNode == oldPointedNode) {
                        join = true;
                        break;
                    }
                }
                if (join == true) {
                    // ok, o arco deve ser juntado
                    newArc.setWeight(joinStringWeight(newArc.getWeight(), oldArc.getWeight()));
                } else {
                    newArcList.add(oldArc);
                }
            }
        }
        theNode.setArcptr(newArcList);

        return theNode;
    }

    private String joinStringWeight(String weight1, String weight2) {
        if ("".equals(weight1) || "".equals(weight2)) {
            if ("".equals(weight1)) {
                return weight2;
            } else {
                return weight1;
            }
        } else {
            return weight1 + " + " + weight2;
        }
    }
}
